package com.yunkeji.system.service.impl;

import cn.hutool.core.bean.BeanUtil;

import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yunkeji.common.constant.CacheConstants;
import com.yunkeji.common.constant.SysIdConstant;
import com.yunkeji.common.core.domain.PageQuery;
import com.yunkeji.common.core.page.TableDataInfo;
import com.yunkeji.common.enums.app.*;
import com.yunkeji.common.exception.AppServiceException;
import com.yunkeji.common.helper.LoginHelper;
import com.yunkeji.common.msg.bo.CallInfo;
import com.yunkeji.common.utils.StringUtils;
import com.yunkeji.common.utils.call.TencentRTCUtils;
import com.yunkeji.common.utils.redis.RedisUtils;
import com.yunkeji.dao.domain.CallLog;
import com.yunkeji.dao.domain.CallRoom;
import com.yunkeji.dao.domain.CallSheetInfo;
import com.yunkeji.dao.mapper.AppUserMapper;
import com.yunkeji.dao.mapper.CallRoomMapper;
import com.yunkeji.dao.mapper.CallSheetInfoMapper;
import com.yunkeji.dao.vo.CallSheetInfoVo;
import com.yunkeji.system.domain.bo.CallSheetInfoBo;
import com.yunkeji.system.service.ICallSheetInfoService;

import lombok.RequiredArgsConstructor;

import org.springframework.stereotype.Service;

import java.util.*;

/**
 * 通话话单Service业务层处理
 *
 * @author zhang
 * @date 2024-07-10
 */
@RequiredArgsConstructor
@Service
public class CallSheetInfoServiceImpl implements ICallSheetInfoService {

    private final CallSheetInfoMapper baseMapper;
    private final CallRoomMapper callRoomMapper;
    private final AppUserMapper appUserMapper;

    /** 查询通话话单 */
    @Override
    public CallSheetInfoVo queryById(Long id) {
        return baseMapper.selectVoById(id);
    }

    /** 查询通话话单列表 */
    @Override
    public TableDataInfo<CallSheetInfoVo> queryPageList(CallSheetInfoBo bo, PageQuery pageQuery) {
        LambdaQueryWrapper<CallSheetInfo> lqw = buildQueryWrapper(bo);
//        Page<CallSheetInfoVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
        Page<CallSheetInfoVo> result = baseMapper.selectListPage(
            bo.getCallerId(),
            bo.getAnswerId(),
            bo.getUcid(),
            bo.getStatus(),
            pageQuery.build());
        return TableDataInfo.build(result);
    }

    /** 查询通话话单列表 */
    @Override
    public List<CallSheetInfoVo> queryList(CallSheetInfoBo bo) {
        LambdaQueryWrapper<CallSheetInfo> lqw = buildQueryWrapper(bo);
        return baseMapper.selectVoList(lqw);
    }

    private LambdaQueryWrapper<CallSheetInfo> buildQueryWrapper(CallSheetInfoBo bo) {
        Map<String, Object> params = bo.getParams();
        LambdaQueryWrapper<CallSheetInfo> lqw = Wrappers.lambdaQuery();
        lqw.eq(bo.getType() != null, CallSheetInfo::getType, bo.getType());
        lqw.eq(bo.getCallerId() != null, CallSheetInfo::getCallPlatformId, bo.getCallerId());
        lqw.eq(bo.getCallerPlat() != null, CallSheetInfo::getCallerPlat, bo.getCallerPlat());
        lqw.eq(bo.getAnswerId() != null, CallSheetInfo::getAnswerPlatformId, bo.getAnswerId());
        lqw.eq(bo.getAnswerPlat() != null, CallSheetInfo::getAnswerPlat, bo.getAnswerPlat());
        lqw.eq(StringUtils.isNotBlank(bo.getUcid()), CallSheetInfo::getUcid, bo.getUcid());
        lqw.eq(bo.getStatus() != null, CallSheetInfo::getStatus, bo.getStatus());
        lqw.eq(bo.getRingTime() != null, CallSheetInfo::getRingTime, bo.getRingTime());
        lqw.eq(bo.getStartTime() != null, CallSheetInfo::getStartTime, bo.getStartTime());
        lqw.eq(bo.getStopTime() != null, CallSheetInfo::getStopTime, bo.getStopTime());
        lqw.eq(bo.getDuration() != null, CallSheetInfo::getDuration, bo.getDuration());
        lqw.eq(bo.getCallPrice() != null, CallSheetInfo::getCallPrice, bo.getCallPrice());
        lqw.eq(bo.getPayPrice() != null, CallSheetInfo::getPayPrice, bo.getPayPrice());
        lqw.eq(bo.getPayCost() != null, CallSheetInfo::getPayCost, bo.getPayCost());
        lqw.eq(bo.getEarnPrice() != null, CallSheetInfo::getEarnPrice, bo.getEarnPrice());
        lqw.eq(bo.getEarnCost() != null, CallSheetInfo::getEarnCost, bo.getEarnCost());
        lqw.eq(StringUtils.isNotBlank(bo.getRecord()), CallSheetInfo::getRecord, bo.getRecord());
        lqw.eq(
                bo.getKeepLiveTimes() != null,
                CallSheetInfo::getKeepLiveTimes,
                bo.getKeepLiveTimes());
        lqw.eq(bo.getPriceTimes() != null, CallSheetInfo::getPriceTimes, bo.getPriceTimes());
        lqw.eq(bo.getScoreFlag() != null, CallSheetInfo::getScoreFlag, bo.getScoreFlag());
        lqw.eq(bo.getCallDate() != null, CallSheetInfo::getCallDate, bo.getCallDate());
        lqw.orderByDesc(CallSheetInfo::getCreateTime);
        return lqw;
    }

    /** 新增通话话单 */
    @Override
    public Boolean insertByBo(CallSheetInfoBo bo) {
        CallSheetInfo add = BeanUtil.toBean(bo, CallSheetInfo.class);
        validEntityBeforeSave(add);
        boolean flag = baseMapper.insert(add) > 0;
        if (flag) {
            bo.setId(add.getId());
        }
        return flag;
    }

    /** 修改通话话单 */
    @Override
    public Boolean updateByBo(CallSheetInfoBo bo) {
        CallSheetInfo update = BeanUtil.toBean(bo, CallSheetInfo.class);
        validEntityBeforeSave(update);
        return baseMapper.updateById(update) > 0;
    }

    /** 保存前的数据校验 */
    private void validEntityBeforeSave(CallSheetInfo entity) {
        // TODO 做一些数据校验,如唯一约束
    }

    /** 批量删除通话话单 */
    @Override
    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
        if (isValid) {
            // TODO 做一些业务上的校验,判断是否需要校验
        }
        return baseMapper.deleteBatchIds(ids) > 0;
    }

    /**
     * 强制挂断正在通话的话单
     * @param id
     * @param remark
     * @return
     */
    @Override
    public Boolean cancel(Long id, String remark) {
        CallSheetInfo callSheetInfo = baseMapper.selectOne(Wrappers.lambdaQuery(CallSheetInfo.class).eq(CallSheetInfo::getId, id));
        if (callSheetInfo == null) {
            throw AppServiceException.sysException("话单状态异常");
        }
        if (!callSheetInfo.getStatus().equals(CallSheetStatus.正在通话.getCode())) {
            throw AppServiceException.sysException("只能挂断正在通话的话单！");
        }

        Long callerId = callSheetInfo.getCallerId();
        Long answerId = callSheetInfo.getAnswerId();
        // 呼叫日志
        CallLog callLog =
            CallLog.builder()
                .createTime(new Date())
                .ucid(callSheetInfo.getUcid())
                .callerId(callerId)
                .answerId(answerId)
                .belongUid(LoginHelper.getUserId())
                .build();
        callLog.setType(CallSheetStatus.已接通系统挂断.getCode());
        callLog.setRemark(CallSheetStatus.已接通系统挂断.name());
        callLog.insert();

        callSheetInfo.setStatus(CallSheetStatus.已接通系统挂断.getCode());
        callSheetInfo.setRemark(remark);
        //执行挂断后的结算流程
        cancelSettle(callSheetInfo);
        // 更新通话时长/支付总数/话单状态
        baseMapper.updateById(callSheetInfo);

        // 重置用户状态,客户端经常出现用户状态混乱,所以不管是在什么情况下调用了了cacelCall都要重置状态,
        // 服务端有补偿机制,客户端在无故不调用cancel的情况下,会自动调用,用户在通话状态会处于忙碌状态,掉线以后,客户端断链,不会改成离线.
        appUserMapper.resetUserStatus(
            callSheetInfo.getAnswerId(),
            AppUserOnlineStatus.忙碌.getCode(),
            AppUserOnlineStatus.空闲.getCode());
        appUserMapper.resetUserStatus(
            callSheetInfo.getCallerId(),
            AppUserOnlineStatus.忙碌.getCode(),
            AppUserOnlineStatus.空闲.getCode());
        return true;
    }

    /**
     * 执行挂断后的结算流程
     *
     * @param callSheetInfo 呼叫单信息
     * @return 通话时长
     */
    private void cancelSettle(CallSheetInfo callSheetInfo) {
        // 设置通话结束时间
        Date date = new Date();
        callSheetInfo.setStopTime(date);
        // 计算通话时长（秒）
        long duration = DateUtil.between(callSheetInfo.getStartTime(), date, DateUnit.SECOND);
        callSheetInfo.setDuration(duration);

        // 清除Redis中与计费相关的数据
        RedisUtils.deleteObject(CacheConstants.CALL_BILL_UPDATE + callSheetInfo.getUcid());
        RedisUtils.deleteObject(
            CacheConstants.CALL_KEEP_LIVE_TIMES_UPDATE + callSheetInfo.getUcid());
        RedisUtils.deleteObject(CacheConstants.CALL_PRICE_TIMES_UPDATE + callSheetInfo.getUcid());

        // 更新并解散通话房间
        CallRoom callRoom =
            callRoomMapper.selectOne(
                Wrappers.lambdaQuery(CallRoom.class)
                    .eq(CallRoom::getUcid, callSheetInfo.getUcid()));
        if (callRoom != null) {
            callRoom.setEndTime(new Date());
            callRoom.setStatus(RoomStatus.房间已解散.getCode());
            callRoomMapper.updateById(callRoom);

            // 解散房间
            TencentRTCUtils.dismissRoom(callRoom.getRoomNum());
            // 停止录音
            TencentRTCUtils.stopRecording(callRoom.getRecordTaskId());
            // 停止录音检测
            TencentRTCUtils.stopCheckVoice(callRoom.getRecordRequestId());
        }

        // 准备平台通知信息
        List<Long> toPlatFormIds = ListUtil.toList(callSheetInfo.getAnswerPlatformId(), callSheetInfo.getCallPlatformId());
        Long fromPlatFormId =  SysIdConstant.系统;

        // 执行挂断通话操作
        CallOperationEnum.挂断通话
            .getApply()
            .apply(
                CallInfo.builder()
                    .duration(duration)
                    .fromPlatFormId(fromPlatFormId)
                    .toPlatFormId(toPlatFormIds)
                    .ucid(callSheetInfo.getUcid())
                    .price(callSheetInfo.getCallPrice())
                    .build());
    }
}
